import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // home: ScrollControllerTestRoute(),
      home: const MyHomePage(
        title: 'demo',
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Column(
        children: [
          const ListTile(title: Text("商品列表")),
          ElevatedButton(
            child: const Text("对话框1"),
            onPressed: () async {
              //弹出对话框并等待其关闭
              bool? delete = await showDeleteConfirmDialog1(context);
              if (delete == null) {
                print("取消删除");
              } else {
                print("已确认删除");
                //... 删除文件
              }
            },
          ),
          ElevatedButton(
            child: const Text("对话框2"),
            onPressed: () async {
              await changeLanguage(context);
            },
          ),
          ElevatedButton(
            child: const Text("对话框3"),
            onPressed: () async {
              await showListDialog(context);
            },
          ),
          ElevatedButton(
            child: const Text("对话框4"),
            onPressed: () async {
              await showDemoDialog(context);
            },
          ),
          DialogRoute(),
          ElevatedButton(
            child: Text("显示底部菜单列表"),
            onPressed: () async {
              int? type = await _showModalBottomSheet(context);
              print(type);
            },
          ),
          ElevatedButton(
            child: Text("显示loading"),
            onPressed: () async {
              int? type = await showLoadingDialog(context);
              print(type);
            },
          ),
          ElevatedButton(
            child: Text("日期选择"),
            onPressed: () async {
              DateTime? type = await _showDatePicker1(context);
              print(type);
            },
          ),
        ],
      ),
    );
  }
}

// showDialog()是Material组件库提供的一个用于弹出Material风格对话框的方法
// 该方法只有两个参数，含义见注释。该方法返回一个Future，它正是用于接收对话框的返回值：如果我们是通过点击对话框遮罩关闭的，则Future的值为null，否则为我们通过Navigator.of(context).pop(result)返回的result值，
// 弹出对话框
Future<bool?> showDeleteConfirmDialog1(context) {
  return showDialog<bool>(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: const Text("提示"),
        content: const Text("您确定要删除当前文件吗?"),
        actions: <Widget>[
          TextButton(
            child: const Text("取消"),
            onPressed: () => Navigator.of(context).pop(), // 关闭对话框,返回null和点击遮罩一样
          ),
          TextButton(
            child: const Text("删除"),
            onPressed: () {
              //关闭对话框并返回true
              Navigator.of(context).pop(true);
            },
          ),
        ],
      );
    },
  );
}

// SimpleDialog也是Material组件库提供的对话框，它会展示一个列表，用于列表选择的场景
Future<void> changeLanguage(context) async {
  int? i = await showDialog<int>(
      context: context,
      builder: (BuildContext context) {
        return SimpleDialog(
          title: const Text('请选择语言'),
          children: <Widget>[
            SimpleDialogOption(
              onPressed: () {
                // 返回1
                Navigator.pop(context, 1);
              },
              child: Padding(
                padding: const EdgeInsets.symmetric(vertical: 6),
                child: const Text('中文简体'),
              ),
            ),
            SimpleDialogOption(
              onPressed: () {
                // 返回2
                Navigator.pop(context, 2);
              },
              child: Padding(
                padding: const EdgeInsets.symmetric(vertical: 6),
                child: const Text('美国英语'),
              ),
            ),
          ],
        );
      });

  if (i != null) {
    print("选择了：${i == 1 ? "中文简体" : "美国英语"}");
  }
}

// 实际上AlertDialog和SimpleDialog都使用了Dialog类。由于AlertDialog和SimpleDialog中使用了IntrinsicWidth来尝试通过子组件的实际尺寸来调整自身尺寸，这就导致他们的子组件不能是延迟加载模型的组件（如ListView、GridView 、 CustomScrollView等）

Future<void> showListDialog(context) async {
  int? index = await showDialog<int>(
    context: context,
    builder: (BuildContext context) {
      var child = Column(
        children: <Widget>[
          ListTile(title: Text("请选择")),
          Expanded(
              child: ListView.builder(
            itemCount: 30,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(
                title: Text("$index"),
                onTap: () => Navigator.of(context).pop(index),
              );
            },
          )),
        ],
      );
      //使用AlertDialog会报错
      //return AlertDialog(content: child);
      // return Dialog(child: child);
      // 自己实现，代替Dialog
      return UnconstrainedBox(
        constrainedAxis: Axis.vertical,
        child: ConstrainedBox(
          constraints: BoxConstraints(maxWidth: 280),
          child: Material(
            child: child,
            type: MaterialType.card,
          ),
        ),
      );
    },
  );
  if (index != null) {
    print("点击了：$index");
  }
}

// 打开一个普通风格的对话框呢（非Material风格） Flutter 提供了一个showGeneralDialog方法
// 遮罩颜色比通过showDialog方法打开的对话框更深。另外对话框打开/关闭的动画已变为缩放动画
Future<T?> showCustomDialog<T>({
  required BuildContext context,
  bool barrierDismissible = true,
  required WidgetBuilder builder,
  ThemeData? theme,
}) {
  final ThemeData theme = Theme.of(context);
  return showGeneralDialog(
    context: context,
    pageBuilder: (BuildContext buildContext, Animation<double> animation,
        Animation<double> secondaryAnimation) {
      final Widget pageChild = Builder(builder: builder);
      return SafeArea(
        child: Builder(builder: (BuildContext context) {
          return theme != null
              ? Theme(data: theme, child: pageChild)
              : pageChild;
        }),
      );
    },
    barrierDismissible: barrierDismissible,
    barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
    barrierColor: Colors.black87, // 自定义遮罩颜色
    transitionDuration: const Duration(milliseconds: 150),
    transitionBuilder: _buildMaterialDialogTransitions,
  );
}

Widget _buildMaterialDialogTransitions(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child) {
  // 使用缩放动画
  return ScaleTransition(
    scale: CurvedAnimation(
      parent: animation,
      curve: Curves.easeOut,
    ),
    child: child,
  );
}

// 调用showCustomDialog
Future<void> showDemoDialog(context) async {
  await showCustomDialog<bool>(
    context: context,
    builder: (context) {
      return AlertDialog(
        title: Text("提示"),
        content: Text("您确定要删除当前文件吗?"),
        actions: <Widget>[
          TextButton(
            child: Text("取消"),
            onPressed: () => Navigator.of(context).pop(),
          ),
          TextButton(
            child: Text("删除"),
            onPressed: () {
              // 执行删除操作
              Navigator.of(context).pop(true);
            },
          ),
        ],
      );
    },
  );
}

class DialogRoute extends StatefulWidget {
  DialogRoute({Key? key}) : super(key: key);

  @override
  State<DialogRoute> createState() => _DialogRouteState();
}

class _DialogRouteState extends State<DialogRoute> {
  bool withTree = false; // 复选框选中状态

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        ElevatedButton(
          child: const Text("对话框5"),
          onPressed: () async {
            bool? delete = await showDeleteConfirmDialog2();
            if (delete == null) {
              print("取消删除");
            } else {
              print("同时删除子目录: $delete");
            }
          },
        ),
      ],
    );
  }

  Future<bool?> showDeleteConfirmDialog2() {
    withTree = false; // 默认复选框不选中
    return showDialog<bool>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text("提示"),
          content: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Text("您确定要删除当前文件吗?"),
              Row(
                children: <Widget>[
                  Text("同时删除子目录？"),
                  // 发现复选框根本选不中！为什么会这样呢？其实原因很简单，我们知道setState方法只会针对当前context的子树重新build，但是我们的对话框并不是在_DialogRouteState的build 方法中构建的，而是通过showDialog单独构建的，所以在_DialogRouteState的context中调用setState是无法影响通过showDialog构建的UI的。另外，我们可以从另外一个角度来理解这个现象，前面说过对话框也是通过路由的方式来实现的，那么上面的代码实际上就等同于企图在父路由中调用setState来让子路由更新，这显然是不行的！简尔言之，根本原因就是context不对。那如何让复选框可点击呢？通常有如下三种方法

                  // Checkbox(
                  //   value: withTree,
                  //   onChanged: (bool? value) {
                  //     //复选框选中状态发生变化时重新构建UI
                  //     setState(() {
                  //       //更新复选框状态
                  //       withTree = !withTree;
                  //     });
                  //   },
                  // ),
                  // 1. 单独抽离出StatefulWidget
                  // DialogCheckbox(
                  //   value: withTree, //默认不选中
                  //   onChanged: (bool? value) {
                  //     //更新选中状态
                  //     withTree = !withTree;
                  //   },
                  // ),
                  //2.使用StatefulBuilder来构建StatefulWidget上下文
                  // StatefulBuilder(
                  //   builder: (context, _setState) {
                  //     return Checkbox(
                  //       value: withTree, //默认不选中
                  //       onChanged: (bool? value) {
                  //         //_setState方法实际就是该StatefulWidget的setState方法，
                  //         //调用后builder方法会重新被调用
                  //         _setState(() {
                  //           //更新选中状态
                  //           withTree = !withTree;
                  //         });
                  //       },
                  //     );
                  //   },
                  // ),
                  // 3. 精妙的解法
                  // Flutter是一个响应式框架，要更新UI只需改变状态后通知框架页面需要重构即可，而Element的markNeedsBuild()方法正是来实现这个功能的！markNeedsBuild()方法会将当前的Element对象标记为“dirty”（脏的），在每一个Frame，Flutter都会重新构建被标记为“dirty”Element对象。既然如此，我们有没有办法获取到对话框内部UI的Element对象，然后将其标示为为“dirty”呢？答案是肯定的！我们可以通过Context来得到Element对象，至于Element与Context的关系我们将会在后面“Flutter核心原理”一章中再深入介绍，现在只需要简单的认为：在组件树中，context实际上就是Element对象的引用。
                  // Checkbox(
                  //   // 依然使用Checkbox组件
                  //   value: withTree,
                  //   onChanged: (bool? value) {
                  //     // 此时context为对话框UI的根Element，我们
                  //     // 直接将对话框UI对应的Element标记为dirty
                  //     (context as Element).markNeedsBuild();
                  //     withTree = !withTree;
                  //   },
                  // ),
                  // 上面的代码运行后复选框也可以正常选中。可以看到，我们只用了一行代码便解决了这个问题！当然上面的代码并不是最优，因为我们只需要更新复选框的状态，而此时的context我们用的是对话框的根context，所以会导致整个对话框UI组件全部rebuild，因此最好的做法是将context的“范围”缩小，也就是说只将Checkbox的Element标记为dirty，优化后的代码为：
                  // 通过Builder来获得构建Checkbox的`context`，
                  // 这是一种常用的缩小`context`范围的方式
                  Builder(
                    builder: (BuildContext context) {
                      return Checkbox(
                        value: withTree,
                        onChanged: (bool? value) {
                          (context as Element).markNeedsBuild();
                          withTree = !withTree;
                        },
                      );
                    },
                  ),
                ],
              ),
            ],
          ),
          actions: <Widget>[
            TextButton(
              child: Text("取消"),
              onPressed: () => Navigator.of(context).pop(),
            ),
            TextButton(
              child: Text("删除"),
              onPressed: () {
                //执行删除操作
                Navigator.of(context).pop(withTree);
              },
            ),
          ],
        );
      },
    );
  }
}

// 2. 使用StatefulBuilder方法

// 对话框上所有可能会改变状态的组件都得单独封装在一个在内部管理状态的StatefulWidget中，这样不仅麻烦，而且复用性不大。因此，我们来想想能不能找到一种更简单的方法？上面的方法本质上就是将对话框的状态置于一个StatefulWidget的上下文中，由StatefulWidget在内部管理，那么我们有没有办法在不需要单独抽离组件的情况下创建一个StatefulWidget的上下文呢？想到这里，我们可以从Builder组件的实现获得灵感。在前面介绍过Builder组件可以获得组件所在位置的真正的Context
// class Builder extends StatelessWidget {
//   const Builder({
//     Key? key,
//     required this.builder,
//   }) : assert(builder != null),
//        super(key: key);
//   final WidgetBuilder builder;

//   @override
//   Widget build(BuildContext context) => builder(context);
// }

// 可以看到，Builder实际上只是继承了StatelessWidget，然后在build方法中获取当前context后将构建方法代理到了builder回调，可见，Builder实际上是获取了StatelessWidget 的上下文（context）。那么我们能否用相同的方法获取StatefulWidget 的上下文，并代理其build方法呢？下面我们照猫画虎，来封装一个StatefulBuilder方法：
class StatefulBuilder extends StatefulWidget {
  const StatefulBuilder({
    Key? key,
    required this.builder,
  })  : assert(builder != null),
        super(key: key);

  final StatefulWidgetBuilder builder;

  @override
  _StatefulBuilderState createState() => _StatefulBuilderState();
}

class _StatefulBuilderState extends State<StatefulBuilder> {
  @override
  Widget build(BuildContext context) => widget.builder(context, setState);
}

// 1. 单独封装一个内部管理选中状态的复选框组件
class DialogCheckbox extends StatefulWidget {
  DialogCheckbox({
    Key? key,
    this.value,
    required this.onChanged,
  });

  final ValueChanged<bool?> onChanged;
  final bool? value;

  @override
  _DialogCheckboxState createState() => _DialogCheckboxState();
}

class _DialogCheckboxState extends State<DialogCheckbox> {
  bool? value;

  @override
  void initState() {
    value = widget.value;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Checkbox(
      value: value,
      onChanged: (v) {
        //将选中状态通过事件的形式抛出
        widget.onChanged(v);
        setState(() {
          //更新自身选中状态
          value = v;
        });
      },
    );
  }
}

// 弹出底部菜单列表模态对话框
Future<int?> _showModalBottomSheet(context) {
  return showModalBottomSheet<int>(
    context: context,
    builder: (BuildContext context) {
      return ListView.builder(
        itemCount: 30,
        itemBuilder: (BuildContext context, int index) {
          return ListTile(
            title: Text("$index"),
            onTap: () => Navigator.of(context).pop(index),
          );
        },
      );
    },
  );
}

// loading提示框 直接通过showDialog+AlertDialog来自定义
showLoadingDialog(context) {
  showDialog(
    context: context,
    barrierDismissible: true, //点击遮罩是否关闭对话框
    builder: (context) {
      return AlertDialog(
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            CircularProgressIndicator(),
            Padding(
              padding: const EdgeInsets.only(top: 26.0),
              child: Text("正在加载，请稍后..."),
            )
          ],
        ),
      );
      // 如果我们嫌Loading框太宽，想自定义对话框宽度，这时只使用SizedBox或ConstrainedBox是不行的，原因是showDialog中已经给对话框设置了最小宽度约束，根据我们在第五章“尺寸限制类容器”一节中所述，我们可以使用UnconstrainedBox先抵消showDialog对宽度的约束，然后再使用SizedBox指定宽度，代码如下：
      // return UnconstrainedBox(
      //   constrainedAxis: Axis.vertical,
      //   child: SizedBox(
      //     width: 280,
      //     child: AlertDialog(
      //       content: Column(
      //         mainAxisSize: MainAxisSize.min,
      //         children: <Widget>[
      //           CircularProgressIndicator(
      //             value: .8,
      //           ),
      //           Padding(
      //             padding: const EdgeInsets.only(top: 26.0),
      //             child: Text("正在加载，请稍后..."),
      //           )
      //         ],
      //       ),
      //     ),
      //   ),
      // );
    },
  );
}

// 日期选择框
Future<DateTime?> _showDatePicker1(context) {
  var date = DateTime.now();
  return showDatePicker(
    context: context,
    initialDate: date,
    firstDate: date,
    lastDate: date.add(
      //未来30天可选
      Duration(days: 30),
    ),
  );
}
